Skip to content

[DRAFT] [starvation] model android::Mutex and recognise custom scoped guards - #2182

Draft
VladimirMakaev wants to merge 1 commit into
facebook:mainfrom
VladimirMakaev:starvation-android-mutex-guards
Draft

VladimirMakaev wants to merge 1 commit into
facebook:mainfrom
VladimirMakaev:starvation-android-mutex-guards

Conversation

@VladimirMakaev

@VladimirMakaev VladimirMakaev commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Starvation knows a fixed list of scoped guards. With others (eg android::Mutex::Autolock), the
constructor's lock was read as a caller lock, so all guards became one lock: inversions were
missed and self deadlocks reported.

#include <mutex>

struct Guard {
  explicit Guard(std::mutex& m) : m_(m) { m_.lock(); }
  ~Guard() { m_.unlock(); }
  std::mutex& m_;
};

class C {
  std::mutex mu1_, mu2_;
 public:
  void thread1() { Guard a(mu1_); Guard b(mu2_); } // false positive: self deadlock
  void thread2() { Guard b(mu2_); Guard a(mu1_); } // same; Infer missed: deadlock with thread1
};

Callee locks are substituted for all arguments and call depths, except through a local's address;
locks held on an object the caller cannot name are kept. A local whose constructor or by-value
return leaves one lock held guards it if its destructor releases one lock. A lock a callee
released before an event is not held by callers there. android::Mutex and Autolock are modelled,
tryLock and timedLock as trylocks; RacerD reports races in classes using them.

Limitations:

  • a destructor that unlocks only on some paths (eg if (owns_)) makes no guard
  • a lock released through a local C guard struct stays held (FP_guard_wm1_wm2_ok)
  • a non-guard local's constructor lock is released at once (FN_acquirer_held_bad)

Test plan

New tests in c/starvation/lock_wrappers.c, cpp/starvation (android_mutex.cpp, custom_guards.cpp,
release_in_callee.cpp, substitution.cpp), cpp/racerd/android_mutex.cpp and
java/starvation/Parameters.java, with FP_/FN_ tests for the limitations. The codetoanalyze tests
pass.

Starvation knows a fixed list of scoped guards. With others (eg android::Mutex::Autolock), the
constructor's lock was read as a caller lock, so all guards became one lock: inversions were
missed and self deadlocks reported.

```cpp
#include <mutex>

struct Guard {
  explicit Guard(std::mutex& m) : m_(m) { m_.lock(); }
  ~Guard() { m_.unlock(); }
  std::mutex& m_;
};

class C {
  std::mutex mu1_, mu2_;
 public:
  void thread1() { Guard a(mu1_); Guard b(mu2_); } // false positive: self deadlock
  void thread2() { Guard b(mu2_); Guard a(mu1_); } // same; Infer missed: deadlock with thread1
};
```

Callee locks are substituted for all arguments and call depths, except through a local's address;
locks held on an object the caller cannot name are kept. A local whose constructor or by-value
return leaves one lock held guards it if its destructor releases one lock. A lock a callee
released before an event is not held by callers there. android::Mutex and Autolock are modelled,
tryLock and timedLock as trylocks; RacerD reports races in classes using them.

Limitations:
- a destructor that unlocks only on some paths (eg `if (owns_)`) makes no guard
- a lock released through a local C guard struct stays held (FP_guard_wm1_wm2_ok)
- a non-guard local's constructor lock is released at once (FN_acquirer_held_bad)

## Test plan

New tests in c/starvation/lock_wrappers.c, cpp/starvation (android_mutex.cpp, custom_guards.cpp,
release_in_callee.cpp, substitution.cpp), cpp/racerd/android_mutex.cpp and
java/starvation/Parameters.java, with FP_/FN_ tests for the limitations. The codetoanalyze tests
pass.
@VladimirMakaev
VladimirMakaev force-pushed the starvation-android-mutex-guards branch from fefb6ff to 62d50d0 Compare October 5, 2026 18:14

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant